package com.javaweb.util.test;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import javax.tools.DiagnosticCollector;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public class DynamicCompilation {

	public static void main(String[] args) throws Exception {
		String sourceCode = """
				    public class Calculator {
				        public int calculate(int x) {
				            return x * 2 + 10;
				        }
				    }
				""";

		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();

		// 使用自定义文件管理器来捕获编译后的类字节码
		StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(diagnostics, null, null);
		DynamicClassLoader classLoader = new DynamicClassLoader();
		ClassFileManager fileManager = new ClassFileManager(standardFileManager, classLoader);

		JavaFileObject source = new JavaSourceFromString("Calculator", sourceCode);
		Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(source);

		JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, null, null,
				compilationUnits);

		boolean success = task.call();
		System.err.println("编译结果: " + success);

		if (success) {
			Class<?> dynamicClass = classLoader.loadClass("Calculator");
			Object instance = dynamicClass.getDeclaredConstructor().newInstance();
			Method method = dynamicClass.getMethod("calculate", int.class); // 注意这里需要指定参数类型
			int result = (Integer) method.invoke(instance, 5); // 传入参数值
			System.out.println("计算结果: " + result);
		} else {
			// 输出编译错误信息
			diagnostics.getDiagnostics().forEach(d -> System.err.println(d));
		}
	}
}

class DynamicClassLoader extends ClassLoader {
	private final Map<String, JavaClassObject> classes = new HashMap<>();

	public void addClass(String name, JavaClassObject classObject) {
		classes.put(name, classObject);
	}

	@Override
	public Class<?> findClass(String name) throws ClassNotFoundException {
		JavaClassObject classObject = classes.get(name);
		if (classObject != null) {
			byte[] bytes = classObject.getBytes();
			return defineClass(name, bytes, 0, bytes.length);
		}
		return super.findClass(name);
	}
}

// 用于存储编译后的类字节码
class JavaClassObject extends SimpleJavaFileObject {
	private final ByteArrayOutputStream bos = new ByteArrayOutputStream();

	public JavaClassObject(String name, Kind kind) {
		super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
	}

	@Override
	public OutputStream openOutputStream() {
		return bos;
	}

	public byte[] getBytes() {
		return bos.toByteArray();
	}
}

// 源代码对象
class JavaSourceFromString extends SimpleJavaFileObject {
	final String code;

	JavaSourceFromString(String name, String code) {
		super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
		this.code = code;
	}

	@Override
	public CharSequence getCharContent(boolean ignoreEncodingErrors) {
		return code;
	}
}

// 自定义文件管理器，用于捕获编译输出
class ClassFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
	private final DynamicClassLoader classLoader;
	private final Map<String, JavaClassObject> compiledClasses = new HashMap<>();

	public ClassFileManager(StandardJavaFileManager standardManager, DynamicClassLoader classLoader) {
		super(standardManager);
		this.classLoader = classLoader;
	}

	@Override
	public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) {
		JavaClassObject classObject = new JavaClassObject(className, kind);
		compiledClasses.put(className, classObject);
		classLoader.addClass(className, classObject);
		return classObject;
	}
	
}